/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is Forte for Java, Community Edition. The Initial * Developer of the Original Code is Sun Microsystems, Inc. Portions * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved. */ package org.netbeans.editor; import java.awt.event.KeyEvent; import java.awt.event.ActionEvent; import java.awt.event.InputEvent; import java.util.Map; import java.util.HashMap; import javax.swing.text.Keymap; import javax.swing.text.JTextComponent; import javax.swing.text.DefaultEditorKit; import javax.swing.KeyStroke; import javax.swing.Action; import javax.swing.AbstractAction; /** * Keymap that is capable to work with MultiKeyBindings * * @author Miloslav Metelka * @version 0.10 */ public class MultiKeymap implements Keymap { /** Action that does nothing */ public static final Action EMPTY_ACTION = new AbstractAction() { public void actionPerformed(ActionEvent evt) { } }; /** Action that beeps. Used for wrong shortcut by default */ public static final Action BEEP_ACTION = new DefaultEditorKit.BeepAction(); /** JTextComponent.DefaultKeymap to be used for processing by this keymap */ private Keymap delegate; /** Context keymap or null for base context */ private Keymap context; /** Ignore possible keyTyped events after context reset */ private boolean ignoreNextTyped = false; /** Action to return when there's no action for incoming key * in some context. This action doesn't occur when no action * is found in base context. */ private Action contextKeyNotFoundAction = BEEP_ACTION; /** Construct new keymap. * @param name name of new keymap */ public MultiKeymap(String name) { delegate = JTextComponent.addKeymap(name, null); } /** Set the context keymap */ void setContext(Keymap contextKeymap) { context = contextKeymap; } /** Reset keymap to base context */ public void resetContext() { context = null; } /** What to do when key is not resolved for context */ public void setContextKeyNotFoundAction(Action a) { contextKeyNotFoundAction = a; } /** Loads the key to action mappings into this keymap in similar way * as JTextComponent.loadKeymap() does. This method is able to handle * MultiKeyBindings but for compatibility it expects * JTextComponent.KeyBinding array. */ public void load(JTextComponent.KeyBinding[] bindings, Action[] actions) { Map h = new HashMap(bindings.length); // add actions to map to resolve by names quickly for (int i = 0; i < actions.length; i++) { Action a = actions[i]; String value = (String)a.getValue(Action.NAME); h.put((value != null ? value : ""), a); // NOI18N } load(bindings, h); } /** Loads key to action mappings into this keymap * @param bindings array of bindings * @param actions map of [action_name, action] pairs */ public void load(JTextComponent.KeyBinding[] bindings, Map actions) { // now create bindings in keymap(s) for (int i = 0; i < bindings.length; i++) { Action a = (Action)actions.get(bindings[i].actionName); if (a != null) { boolean added = false; if (bindings[i] instanceof MultiKeyBinding) { MultiKeyBinding mb = (MultiKeyBinding)bindings[i]; if (mb.keys != null) { Keymap cur = delegate; for (int j = 0; j < mb.keys.length; j++) { if (j == mb.keys.length - 1) { // last keystroke in sequence cur.addActionForKeyStroke(mb.keys[j], a); } else { // not the last keystroke Action sca = cur.getAction(mb.keys[j]); if (!(sca instanceof KeymapSetContextAction)) { sca = new KeymapSetContextAction(JTextComponent.addKeymap(null, null)); cur.addActionForKeyStroke(mb.keys[j], sca); } cur = ((KeymapSetContextAction)sca).contextKeymap; } } added = true; } } if (!added) { if (bindings[i].key != null) { delegate.addActionForKeyStroke(bindings[i].key, a); } else { // key is null -> set default action setDefaultAction(a); } } } } } public String getName() { return (context != null) ? context.getName() : delegate.getName(); } /** Get default action of this keymap or parent keymap if this * one doesn't have one. Context keymap can have default action * but it will be not used. */ public Action getDefaultAction() { return delegate.getDefaultAction(); } public void setDefaultAction(Action a) { if (context != null) { context.setDefaultAction(a); } else { delegate.setDefaultAction(a); } } Action getActionImpl(KeyStroke key) { Action a = null; if (context != null) { a = context.getAction(key); if (a == null) { // possibly ignore modifier keystrokes switch (key.getKeyCode()) { case KeyEvent.VK_SHIFT: case KeyEvent.VK_CONTROL: case KeyEvent.VK_ALT: case KeyEvent.VK_META: return EMPTY_ACTION; } if (key.isOnKeyRelease() || key.getKeyChar() != 0) { return EMPTY_ACTION; // ignore releasing and typed events } } } else { a = delegate.getAction(key); } return a; } public Action getAction(KeyStroke key) { Action ret = null; // Explicit patches of the keyboard problems if (ignoreNextTyped) { if (key.isOnKeyRelease()) { // ignore releasing here ret = EMPTY_ACTION; } else { // either pressed or typed ignoreNextTyped = false; } if (key.getKeyChar() != 0) { // for valid key character here ret = EMPTY_ACTION; // prevent using defaultAction } } if (ret == null) { ret = getActionImpl(key); if (ret != EMPTY_ACTION) { // key that should be ignored if (!(ret instanceof KeymapSetContextAction)) { if (context != null) { ignoreNextTyped = true; } else if ( // Explicit patch for the keyTyped sent after Alt+key (key.getModifiers() & InputEvent.ALT_MASK) != 0 // Alt pressed && (key.getModifiers() & InputEvent.CTRL_MASK) == 0 // Ctrl not pressed ) { boolean patch = true; if (key.getKeyChar() == 0) { switch (key.getKeyCode()) { case KeyEvent.VK_ALT: // don't patch single Alt case KeyEvent.VK_KANJI: case KeyEvent.VK_KATAKANA: case KeyEvent.VK_HIRAGANA: case KeyEvent.VK_JAPANESE_KATAKANA: case KeyEvent.VK_JAPANESE_HIRAGANA: case 0x0107: // KeyEvent.VK_INPUT_METHOD_ON_OFF: - in 1.3 only patch = false; break; } } if (patch) { ignoreNextTyped = true; } } resetContext(); // reset context when resolved } if (context != null && ret == null) { // no action found when in context ret = contextKeyNotFoundAction; } } } // Explicit patch for Ctrl+Space - eliminating the additional KEY_TYPED sent if (key == KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, InputEvent.CTRL_MASK)) { ignoreNextTyped = true; } /* System.out.println("key=" + key + ", keyChar=" + (int)key.getKeyChar() + ", keyCode=" + key.getKeyCode() + ", keyModifiers=" + key.getModifiers() // NOI18N + ", ignoreNextTyped=" + ignoreNextTyped + ", context=" + context // NOI18N + ", returning action=" + ((ret == EMPTY_ACTION) ? "EMPTY_ACTION" : ((ret == null) ? "null" : ((ret instanceof javax.swing.text.TextAction) // NOI18N ? ret.getValue(javax.swing.Action.NAME) : ret.getClass())))); */ return ret; } public KeyStroke[] getBoundKeyStrokes() { return (context != null) ? context.getBoundKeyStrokes() : delegate.getBoundKeyStrokes(); } public Action[] getBoundActions() { return (context != null) ? context.getBoundActions() : delegate.getBoundActions(); } public KeyStroke[] getKeyStrokesForAction(Action a) { return (context != null) ? context.getKeyStrokesForAction(a) : delegate.getKeyStrokesForAction(a); } public boolean isLocallyDefined(KeyStroke key) { return (context != null) ? context.isLocallyDefined(key) : delegate.isLocallyDefined(key); } public void addActionForKeyStroke(KeyStroke key, Action a) { if (context != null) { context.addActionForKeyStroke(key, a); } else { delegate.addActionForKeyStroke(key, a); } } public void removeKeyStrokeBinding(KeyStroke key) { if (context != null) { context.removeKeyStrokeBinding(key); } else { delegate.removeKeyStrokeBinding(key); } } public void removeBindings() { if (context != null) { context.removeBindings(); } else { delegate.removeBindings(); } } public Keymap getResolveParent() { return (context != null) ? context.getResolveParent() : delegate.getResolveParent(); } public void setResolveParent(Keymap parent) { if (context != null) { context.setResolveParent(parent); } else { delegate.setResolveParent(parent); } } public String toString() { return "MK: name=" + getName(); // NOI18N } /** Internal class used to set the context */ class KeymapSetContextAction extends AbstractAction { Keymap contextKeymap; static final long serialVersionUID =1034848289049566148L; KeymapSetContextAction(Keymap contextKeymap) { this.contextKeymap = contextKeymap; } public void actionPerformed(ActionEvent evt) { setContext(contextKeymap); } } } /* * Log * 16 Gandalf-post-FCS1.13.1.1 4/7/00 Miloslav Metelka more japanese * corrections * 15 Gandalf-post-FCS1.13.1.0 4/6/00 Miloslav Metelka Japanese input methods * 14 Gandalf 1.13 1/13/00 Miloslav Metelka * 13 Gandalf 1.12 1/10/00 Miloslav Metelka * 12 Gandalf 1.11 12/28/99 Miloslav Metelka * 11 Gandalf 1.10 11/8/99 Miloslav Metelka * 10 Gandalf 1.9 10/23/99 Ian Formanek NO SEMANTIC CHANGE - Sun * Microsystems Copyright in File Comment * 9 Gandalf 1.8 10/10/99 Miloslav Metelka * 8 Gandalf 1.7 9/15/99 Miloslav Metelka * 7 Gandalf 1.6 8/17/99 Miloslav Metelka * 6 Gandalf 1.5 8/9/99 Ian Formanek Generated Serial Version * UID * 5 Gandalf 1.4 6/22/99 Miloslav Metelka * 4 Gandalf 1.3 4/23/99 Miloslav Metelka Undo added and internal * improvements * 3 Gandalf 1.2 3/30/99 Miloslav Metelka * 2 Gandalf 1.1 3/27/99 Miloslav Metelka * 1 Gandalf 1.0 3/23/99 Miloslav Metelka * $ */